﻿using System;
using System.Diagnostics;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Discovery;
using System.ServiceModel.Routing;
using EasyComm.Base.Hosting;
using EasyComm.Contracts;
using EasyComm.Hosting.Behavior;
using EasyComm.Utilities;

namespace EasyComm.Hosting.Configuration
{
    public static class GenericConfiguration
    {
        internal static Uri ApplyConfiguration(EasyCommServiceHostBase serviceHost, Type serviceType, Type[] serviceContractList, Uri[] discoveryProxyAddresses = null, bool udpAnnouncement = true, bool useMex = true, bool debug = true, string ns = "http://www.tempuri.org/")
        {
            if (serviceHost == null)
            {
                return null;
            }

            if (serviceHost.BaseAddresses.Count != 1)
            {
                throw new FaultException("Current implementation allows one base address.");
            }

            Uri listenUri = null;
            #region main endpoints
            for (int i = 0; i < serviceHost.BaseAddresses.Count; i++)
            {
                if (serviceType == typeof(DiscoveryProxy) ||
                    (serviceType.BaseType != null && serviceType.BaseType == typeof(DiscoveryProxy)))
                {
                    bool isNLB = serviceHost as NLBProxyServiceHost != null;
                    listenUri = isNLB ? new Uri(serviceHost.BaseAddresses[i], "/NLBProxy.svc") : new Uri(serviceHost.BaseAddresses[i], "/SLBProxy.svc");
                    DiscoveryEndpoint discoveryEndPoint = new DiscoveryEndpoint(GenericConfiguration.GetMainBinding(), new EndpointAddress(listenUri));
                    discoveryEndPoint.IsSystemEndpoint = false;
                    serviceHost.AddServiceEndpoint(discoveryEndPoint);
                    if (!isNLB)
                    {
                        //slb proxy
                        serviceHost.Description.Behaviors.Add(new SLBDiscoveryBehavior(serviceContractList[0], discoveryProxyAddresses[0]));
                    }
                }
                else if (serviceType == typeof(RoutingService))
                {
                    if (discoveryProxyAddresses == null || discoveryProxyAddresses.Length != 1)
                    {
                        throw new FaultException("NLB Router must be started having a valid proxy address.");
                    }
                    listenUri = new Uri(serviceHost.BaseAddresses[i], "/Router.svc");
                    ContractDescription routerContractDesc = ContractDescription.GetContract(typeof(IRequestReplyRouter), serviceType);
                    ServiceEndpoint routerEndPoint = new ServiceEndpoint(routerContractDesc, GenericConfiguration.GetMainBinding(), new EndpointAddress(listenUri));
                    serviceHost.AddServiceEndpoint(routerEndPoint);

                    serviceHost.Description.Behaviors.Add(new RoutingBehavior(new RoutingConfiguration()));
                    serviceHost.Description.Behaviors.Add(new NLBDiscoveryBehavior(serviceContractList, new Type[] { typeof(IAdminSLBContract) }, discoveryProxyAddresses[0]));
                }
                else if (serviceContractList != null)
                {
                    foreach (Type serviceContract in serviceContractList)
                    {
                        if (serviceContract == typeof(IAdminNLBContract) ||
                            serviceContract == typeof(IAdminSLBContract))
                        {
                            listenUri = new Uri(serviceHost.BaseAddresses[i], "/AdminService.svc");
                        }
                        else if (serviceContract == typeof(IAdminWorkerContract))
                        {
                            listenUri = new Uri(serviceHost.BaseAddresses[i], string.Format("{0}/WorkerAdmin.svc", Process.GetCurrentProcess().Id));
                        }
                        else
                        {
                            listenUri = new Uri(serviceHost.BaseAddresses[i], string.Format("{0}/WorkerService.svc", Process.GetCurrentProcess().Id));
                        }
                        serviceHost.AddServiceEndpoint(serviceContract, GenericConfiguration.GetMainBinding(), listenUri);
                    }
                    listenUri = null;
                }
            }
            #endregion

            #region Discovery
            ServiceDiscoveryBehavior discoveryBehavior = null;
            if (serviceType != typeof(RoutingService) && discoveryProxyAddresses != null && discoveryProxyAddresses.Length > 0)
            {
                discoveryBehavior = new ServiceDiscoveryBehavior();
                foreach (Uri discoveryUri in discoveryProxyAddresses)
                {
                    if (!NetUtils.TCPOnlineCheck(discoveryUri.Host, discoveryUri.Port))
                    {
                        Trace.WriteLine(string.Format("Discovery proxy server '{0}' is offline. Bypassing registration for proxy announcement.", discoveryUri));
                        continue;
                    }
                    AnnouncementEndpoint announcementEndpoint = new AnnouncementEndpoint(GenericConfiguration.GetAnnouncementBinding(), new EndpointAddress(discoveryUri.ToString()));
                    discoveryBehavior.AnnouncementEndpoints.Add(announcementEndpoint);
                }
            }

            if (udpAnnouncement)
            {
                if (discoveryBehavior == null)
                {
                    discoveryBehavior = new ServiceDiscoveryBehavior();
                }
                discoveryBehavior.AnnouncementEndpoints.Add(new UdpAnnouncementEndpoint());
                serviceHost.AddServiceEndpoint(new UdpDiscoveryEndpoint() { Name = "udpDiscoveryEndPoint" });
            }

            if (discoveryBehavior != null)
            {
                serviceHost.Description.Behaviors.Add(discoveryBehavior);
            }
            #endregion

            if (useMex)
            {
                GenericConfiguration.SetupMex(serviceHost, serviceType, string.Format("{0}_MexUrl", serviceType.Name));
            }
            if (debug)
            {
                GenericConfiguration.SetupDebugging(serviceHost);
            }
            //System.ServiceModel.ServiceHostingEnvironment.MultipleSiteBindingsEnabled = true;
            return listenUri;
        }

        internal static void SetupMex(ServiceHost serviceHost, Type serviceType, string mexUrlKey = "mexUrl")
        {
            if (serviceHost == null)
            {
                return;
            }
            ServiceMetadataBehavior smb = serviceHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
            if (smb == null)
            {
                smb = new ServiceMetadataBehavior();
            }
            Uri mexAddress = NetUtils.GetAvailableLocalEndPoint(mexUrlKey, string.Format("{0}/mex", serviceType.Name), ProtocolScheme.Http);
            smb.HttpGetEnabled = true;
            smb.HttpGetUrl = mexAddress;
            smb.MetadataExporter.PolicyVersion = PolicyVersion.Policy15;
            serviceHost.Description.Behaviors.Add(smb);
            serviceHost.AddServiceEndpoint(
              ServiceMetadataBehavior.MexContractName,
              MetadataExchangeBindings.CreateMexHttpBinding(),
              mexAddress);
        }

        internal static void SetupDebugging(ServiceHost serviceHost)
        {
            if (serviceHost == null)
            {
                return;
            }
            ServiceDebugBehavior sdb = serviceHost.Description.Behaviors.Find<ServiceDebugBehavior>();
            if (sdb == null)
            {
                sdb = new ServiceDebugBehavior();
            }
            sdb.IncludeExceptionDetailInFaults = true;
            serviceHost.Description.Behaviors.Add(sdb);

            //serviceHost.Extensions.
            //System.Diagnostics.so..TraceListener..SourceFilter
        }

        //internal static ContractDescription CreateContractDescription(Type serviceType, Type contractType, string ns = "http://www.tempuri.org/", bool contractAlreadyImplemented = false)
        //{
        //    if (contractType == null)
        //    {
        //        throw new ArgumentException("contractType is null or not an object.");// ApplicationException("A service must inherit exactly one contract interface!");
        //        //Type[] contractInterfaces = serviceType.GetInterfaces();
        //        //if (contractInterfaces.Length != 1)
        //        //{
        //        //}
        //        //contractType = contractInterfaces[0];
        //    }

        //    ContractDescription contractDescription = new ContractDescription(contractType.Name, contractType.Namespace);
        //    contractDescription.ProtectionLevel = ProtectionLevel.None;
        //    contractDescription.ContractType = contractType;
        //    contractDescription.ConfigurationName = contractType.FullName;
        //    contractDescription.SessionMode = SessionMode.Allowed;

        //    if (contractAlreadyImplemented)
        //    {
        //        return contractDescription;
        //    }

        //    MethodInfo[] methods = contractType.GetMethods(BindingFlags.Public | BindingFlags.Instance);

        //    foreach (MethodInfo method in methods)
        //    {
        //        OperationDescription operationDescription = new OperationDescription(method.Name, contractDescription);

        //        string messagePath = ns + contractType.Name + "/";
        //        string requestName = method.Name;
        //        string responseName = method.Name + "Response";

        //        string requestPath = messagePath + requestName;
        //        string responsePath = messagePath + responseName + "Response";
        //        string resultName = requestName + "Result";

        //        MessageDescription inputMessage = new MessageDescription(requestPath, MessageDirection.Input);
        //        inputMessage.Body.WrapperName = requestName;
        //        inputMessage.Body.WrapperNamespace = ns;

        //        ParameterInfo[] parameters = method.GetParameters();
        //        foreach (ParameterInfo parameter in parameters)
        //        {
        //            MessagePartDescription messagePartDescription =
        //                new MessagePartDescription(parameter.Name, ns);
        //            messagePartDescription.Type = parameter.ParameterType;
        //            inputMessage.Body.Parts.Add(messagePartDescription);
        //        }
        //        operationDescription.Messages.Add(inputMessage);

        //        if (null != method.ReturnType)
        //        {
        //            MessageDescription outputMessage =
        //                new MessageDescription(responsePath, MessageDirection.Output);
        //            outputMessage.Body.ReturnValue = new MessagePartDescription(resultName, ns);
        //            outputMessage.Body.ReturnValue.Type = method.ReturnType;
        //            outputMessage.Body.WrapperName = responseName;
        //            outputMessage.Body.WrapperNamespace = ns;
        //            operationDescription.Messages.Add(outputMessage);
        //        }

        //        contractDescription.Operations.Add(operationDescription);
        //        operationDescription.Behaviors.Add(new ServiceOperationBehavior(method));

        //        OperationBehaviorAttribute operationBehaviourAttribute = new OperationBehaviorAttribute();
        //        operationDescription.Behaviors.Add(operationBehaviourAttribute);

        //        DataContractSerializerOperationBehavior d = new DataContractSerializerOperationBehavior(operationDescription);
        //        operationDescription.Behaviors.Add(d);
        //    }

        //    return contractDescription;
        //}

        //internal static ContractDescription CreateContractDescription(Type serviceType)
        //{
        //    ContractDescription contractDescription = new ContractDescription(serviceType.Name);
        //    contractDescription.ProtectionLevel = ProtectionLevel.None;
        //    contractDescription.SessionMode = SessionMode.Allowed;
        //    return contractDescription;
        //}

        internal static Binding GetAnnouncementBinding()
        {
            NetTcpBinding tcpBinding = new NetTcpBinding();
            tcpBinding.Name = "NetTcpBindingConfiguration";
            tcpBinding.OpenTimeout = new TimeSpan(0, 10, 0);
            tcpBinding.CloseTimeout = new TimeSpan(0, 1, 0);
            tcpBinding.ReceiveTimeout = new TimeSpan(0, 10, 0);
            tcpBinding.SendTimeout = new TimeSpan(0, 10, 0);
            tcpBinding.MaxBufferPoolSize = int.MaxValue;
            tcpBinding.MaxBufferSize = int.MaxValue;
            tcpBinding.MaxReceivedMessageSize = int.MaxValue;
            tcpBinding.TransferMode = TransferMode.Buffered;
            tcpBinding.TransactionFlow = true;
            tcpBinding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
            tcpBinding.PortSharingEnabled = false;
            tcpBinding.MaxConnections = 15;
            tcpBinding.ListenBacklog = 15;
            tcpBinding.Security = new NetTcpSecurity() { Mode = SecurityMode.None, Transport = new TcpTransportSecurity() { ClientCredentialType = TcpClientCredentialType.Windows, ProtectionLevel = ProtectionLevel.EncryptAndSign } };

            tcpBinding.ReaderQuotas.MaxDepth = 32;
            tcpBinding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
            tcpBinding.ReaderQuotas.MaxArrayLength = int.MaxValue;
            tcpBinding.ReaderQuotas.MaxBytesPerRead = 4096;
            tcpBinding.ReaderQuotas.MaxNameTableCharCount = int.MaxValue;
            tcpBinding.ReliableSession.Ordered = true;
            tcpBinding.ReliableSession.InactivityTimeout = new TimeSpan(0, 10, 0);
            tcpBinding.ReliableSession.Enabled = false;

            return tcpBinding;
        }

        public static Binding GetMainBinding()
        {
            NetTcpBinding tcpBinding = new NetTcpBinding();
            tcpBinding.Name = "NetTcpBindingConfiguration";
            tcpBinding.OpenTimeout = new TimeSpan(0, 10, 0);
            tcpBinding.CloseTimeout = new TimeSpan(0, 1, 0);
            tcpBinding.ReceiveTimeout = new TimeSpan(0, 10, 0);
            tcpBinding.SendTimeout = new TimeSpan(0, 10, 0);
            tcpBinding.MaxBufferPoolSize = int.MaxValue;
            tcpBinding.MaxBufferSize = int.MaxValue;
            tcpBinding.MaxReceivedMessageSize = int.MaxValue;
            tcpBinding.TransferMode = TransferMode.Buffered;
            tcpBinding.TransactionFlow = true;
            tcpBinding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
            tcpBinding.PortSharingEnabled = false;
            tcpBinding.MaxConnections = 15;
            tcpBinding.ListenBacklog = 15;
            tcpBinding.Security = new NetTcpSecurity() { Mode = SecurityMode.None, Transport = new TcpTransportSecurity() { ClientCredentialType = TcpClientCredentialType.Windows, ProtectionLevel = ProtectionLevel.EncryptAndSign } };

            tcpBinding.ReaderQuotas.MaxDepth = 32;
            tcpBinding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
            tcpBinding.ReaderQuotas.MaxArrayLength = int.MaxValue;
            tcpBinding.ReaderQuotas.MaxBytesPerRead = 4096;
            tcpBinding.ReaderQuotas.MaxNameTableCharCount = int.MaxValue;
            tcpBinding.ReliableSession.Ordered = true;
            tcpBinding.ReliableSession.InactivityTimeout = new TimeSpan(0, 10, 0);
            tcpBinding.ReliableSession.Enabled = false;

            return tcpBinding;
        }
    }
}
